#include "standardchanmanager.h"

#include <QSettings>
#include <QFile>
#include <QMessageBox>
#include "components/board.h"
#include "components/thread.h"
#include "components/post.h"
#include "postitemmodel.h"
#include "threaditemmodel.h"
#include "sqlitethreadstorage.h"
#include "log.h"
#include "imageview.h"
#include "mainwindow.h"
#include "boardparsers/parserfactory.h"
#include "chanagents/agentfactory.h"
#include "analyzers/extractor.h"
#include "captchafetchers/captchafetcherfactory.h"
#include "filterlist.h"
#include "filtertext.h"
#include "paths.h"
#include "standardrequestmanager.h"
#include "httpnetworkmedium.h"
#include "chanwatcher.h"
#include "postitemview.h"
#include "notification/notificationmanager.h"

namespace Wishmaster {

        StandardChanManager::StandardChanManager(MainWindow* parent, ThreadStorage* threadStorage) :
			m_view(parent)
	{
		m_newMsgTimer.setInterval(5000); // FIXME hardcoded
		m_newMsgTimer.setSingleShot(true);
		connect(&m_newMsgTimer, SIGNAL(timeout()), this, SLOT(displayNewMsgNotification()));

		QSettings settings;
		m_imageView = NULL;

		m_threadStorage = threadStorage;
		m_threadStorage->setMaxThreads(settings.value("threadstorage/keepthreads", 100).toInt());

		m_extractor = new Extractor();
		m_taskhub = new Taskhub();
		connect(m_taskhub, SIGNAL(totalProgress(int)), this, SLOT(progressUpdate(int)));
		m_watcher = new ChanWatcher();
		m_chanList = new ChanList();

		int qlength = 5;
		qlength = settings.value("connection/queueLength", qlength).toInt();
		m_requestManager = new Wishmaster2::Network::StandardRequestManager();
		m_requestManager->registerMedium(new Wishmaster2::Network::HttpNetworkMedium());
		QDir networkContextsDirectory(DefaultPath::storageRoot() + QDir::separator() + "network_contexts");
		if(!networkContextsDirectory.exists())
			networkContextsDirectory.mkpath(".");
		m_requestManager->loadSettings(networkContextsDirectory.path().toStdString());

		m_contentPipe = new ContentPipe(this);
		connect(m_contentPipe, SIGNAL(chanIconChanged(QString)), this, SLOT(chanIconChanged(QString)));
		
		m_captchaFetcherFactory = new CaptchaFetcherFactory();
		m_captchaFetcherFactory->loadPlugins();
	}

	StandardChanManager::~StandardChanManager()
	{
		QDir networkContextsDirectory(DefaultPath::storageRoot() + QDir::separator() + "network_contexts");
		if(!networkContextsDirectory.exists())
			networkContextsDirectory.mkpath(".");
		m_requestManager->saveSettings(networkContextsDirectory.path().toStdString());

		delete m_watcher;
		delete m_chanList;
		if(m_threadStorage)
			delete m_threadStorage;
		delete m_taskhub;
	}

	bool StandardChanManager::initialize()
	{
		m_extractor->initialize();

		m_watcher->loadSettings();
		m_watcher->setManager(this);
		m_watcher->setView(m_view);
		m_watcher->startTimers();

		return true;
	}

	bool StandardChanManager::shutdown()
	{
		m_watcher->saveSettings();
		if(m_filterList)
		{
			QSettings s;
			s.setValue("filters", m_filterList->save());
		}
		return true;
	}

	bool StandardChanManager::requestFavicon(const QString& chanName)
	{
		Chan* chan = chanlist()->getChan(chanName);
		if(!chan)
			return false;

		if(!chan->agent())
			return false;

		chan->agent()->requestFavicon();

		return true;
	}

	bool StandardChanManager::requestBoardIndex(const QString& chanName, const QString& boardId, int page)
	{
		Chan* chan = chanlist()->getChan(chanName);
		if(!chan)
			return false;

		if(!chan->board(boardId))
			return false;

		if(!chan->agent())
			return false;

		chan->agent()->requestBoardIndex(boardId, page);

		return true;
	}

	bool StandardChanManager::requestThread(const ThreadId& threadId)
	{
		Chan* chan = chanlist()->getChan(threadId.chanName());
		if(!chan)
			return false;

		ThreadPtr thread = chanlist()->getThread(threadId);
		if(!thread)
			return false;

		chan->agent()->requestThread(threadId, thread->lastPostId());

		return true;
	}

	bool StandardChanManager::requestThumbnail(const PostId& postId, int attachmentIndex, const QUrl& url)
	{
		Chan* chan = chanlist()->getChan(postId.chan());
		if(!chan)
			return false;

		if(!chan->agent())
			return false;

		if(url.isEmpty())
			return false;

		chan->agent()->requestThumbnail(postId, attachmentIndex, url);
		return true;
	}

	bool StandardChanManager::requestPic(const PostId& postId, int attachmentIndex)
	{
		Chan* chan = chanlist()->getChan(postId.chan());
		if(!chan)
			return false;

		if(!chan->agent())
			return false;

		PostPtr post = chanlist()->getPost(postId);
		if(!post)
			return false;

		if(post->attachmentCount() <= attachmentIndex)
			return false;

		AttachmentPtr attach = post->attachment(attachmentIndex);
		if(!attach)
			return false;

		if(!attach->type().startsWith("image"))
			return false;

		chan->agent()->requestPic(postId, attachmentIndex, attach->url());
		return true;
	}

	bool StandardChanManager::downloadFile(const PostId& postId, int attachmentIndex, const QString& filename)
	{
		Chan* chan = chanlist()->getChan(postId.chan());
		if(!chan)
			return false;

		if(!chan->agent())
			return false;

		PostPtr post = chanlist()->getPost(postId);
		if(!post)
			return false;

		if(post->attachmentCount() <= attachmentIndex)
			return false;

		AttachmentPtr attach = post->attachment(attachmentIndex);
		if(!attach)
			return false;

		chan->agent()->downloadFile(postId, attachmentIndex, attach->url(), filename);
		return true;
	}

	bool StandardChanManager::requestCaptcha(const ThreadId& threadId)
	{
		Chan* chan = chanlist()->getChan(threadId.chanName());
		if(!chan)
			return false;

		if(!chan->agent())
			return false;

		chan->agent()->getCaptcha(threadId);
		return true;
	}

	bool StandardChanManager::addPost(const ThreadId& threadId, const PostDescriptor& post, const QString& captcha)
	{
		Chan* chan = chanlist()->getChan(threadId.chanName());
		chan->agent()->makePost(post, captcha);
		return true;
	}

	bool StandardChanManager::createThread(const QString& chanName, const QString& boardId, const PostDescriptor& post, const QString& captcha)
	{
		if((post.parent_thread.chanName() != chanName) || (post.parent_thread.boardId() != boardId))
			return false;
		Chan* chan = chanlist()->getChan(chanName);
		chan->agent()->createThread(post, captcha);
		return true;
	}

	Taskhub* StandardChanManager::taskhub()
	{
		return m_taskhub;
	}

	Connector* StandardChanManager::connector()
	{
		return 0;
	}

	bool StandardChanManager::loadChanList(const QString& filename, AgentFactory* agentFactory)
	{
		log(llNormal, "Loading chanlist from: %s", qPrintable(filename));
		QFile cfg(filename);
		if(!cfg.open(QIODevice::ReadOnly))
		{
			QMessageBox::critical(NULL, tr("Error"), tr("File 'chans.xml' is missing. This file contains chans settings, please verify that you have this file in the same directory with wishmaster executable"));
			return false;
		}
		m_chanlistFile = filename;
		return loadChanList(cfg.readAll(), agentFactory);
	}

	bool StandardChanManager::loadChanList(const QByteArray& data, AgentFactory* agentFactory)
	{
		m_agentFactory = agentFactory;
		QDomDocument document;
		document.setContent(data);
		QDomNodeList list = document.childNodes().at(1).childNodes();
		for(unsigned int i = 0; i < list.length(); i++)
		{
			log(llDebug, "Node %d/%d", i, list.length());
			QDomNode node = list.at(i);
			if(node.nodeName() == "chan")
			{
				if(!parseChanNode(node, agentFactory))
				{
					log(llDebug, "Unable to parse node %d", i);
				}
			}
			log(llDebug, "Node %d/%d done", i, list.length());
		}
		emit chanListChanged(allChanNames());
		return true;
	}

	bool StandardChanManager::parseChanNode(const QDomNode& node, AgentFactory* agentFactory)
	{
		QString disabled = node.attributes().namedItem("disabled").toAttr().value();
		if(disabled == "true")
			return true;

		QString host = node.attributes().namedItem("url").toAttr().value();
		QString agent = node.attributes().namedItem("agent").toAttr().value();
		QString parser = node.attributes().namedItem("parser").toAttr().value();
		QString timestampFormat = node.attributes().namedItem("timestamp_format").toAttr().value();

		//log(llDebug, "Chan: %s", qPrintable(node.attributes().namedItem("url").toAttr().value()));
		ChanAgent* ag = agentFactory->makeAgent(agent, host);
		if(!ag)
			return false;
		ag->setRequestManager(m_requestManager);
		Chan* newChan = new Chan(host, ag, this);
		parseArgs(node.toElement(), newChan);
		QDomNodeList boards = node.childNodes();
		for(unsigned int j = 0; j < boards.length(); j++)
		{
			QDomNode board = boards.at(j);
			loadBoard(newChan, ag, parser, board, timestampFormat);
		}
		connect(ag, SIGNAL(favicon(QString,QByteArray)), m_contentPipe, SLOT(contentFavicon(QString,QByteArray)));
		connect(ag, SIGNAL(board(QString,QString,int,QByteArray)), m_contentPipe, SLOT(contentBoardIndex(QString,QString,int,QByteArray)));
		connect(ag, SIGNAL(thread(ThreadId,QByteArray)), m_contentPipe, SLOT(contentThread(ThreadId,QByteArray)));
		connect(ag, SIGNAL(thumb(PostId,int,QByteArray)), m_contentPipe, SLOT(contentThumbnail(PostId,int,QByteArray)));
		connect(ag, SIGNAL(attachment(PostId,int,QByteArray,QString)), m_contentPipe, SLOT(contentAttachment(PostId,int,QByteArray,QString)));
		connect(ag, SIGNAL(captcha(ThreadId,QByteArray)), this, SLOT(slot_captcha(ThreadId,QByteArray)));
		connect(ag, SIGNAL(postingDone(ThreadId)), this, SLOT(postingDone(ThreadId)));

		chanlist()->addChan(newChan);
		newChan->setThreadStorage(m_threadStorage);
		newChan->setExtractor(m_extractor);
		newChan->load();
		requestFavicon(newChan->name());
		return true;
	}

	QStringList StandardChanManager::allChanNames() const
	{
		QStringList list;
		for(int i = 0; i < chanlist()->chansCount(); i++)
		{
			list.append(chanlist()->getChan(i)->getHost());
		}
		return list;
	}

	void StandardChanManager::refresh(bool ignoreOverrides, bool indexOnly)
	{
		for(int i = 0; i < chanlist()->chansCount(); i++)
		{
		//	chanlist()->getChan(i)->refresh(ignoreOverrides, indexOnly);
		}
	}

	void StandardChanManager::updateContent(const QString& chanName, const QString& boardId)
	{
		emit contentChanged(chanName, boardId);
	}

	void StandardChanManager::updateContent(const PostId& postId, int attachmentIndex)
	{
		emit contentChanged(postId, attachmentIndex);
	}

	void StandardChanManager::updateAttachment(const PostId& postId, int attachmentIndex)
	{
		showAttachment(postId, attachmentIndex);
	}

	void StandardChanManager::setImageView(ImageViewForm* imageview)
	{
		m_imageView = imageview;
	}

//	void StandardChanManager::refreshCurrentBoard()
//	{
//		m_view->getActiveChan()->refresh(m_view->getActiveBoard(), ThreadId(), true);
//	}

	void StandardChanManager::collectGarbage()
	{
		for(int i = 0; i < chanlist()->chansCount(); i++)
		{
			chanlist()->getChan(i)->collectGarbage();
		}
	}

	void StandardChanManager::showPicture(const PostId& postId, int attachmentIndex)
	{
		if(attachmentIndex == Attachment::NoAttachment)
			return;

		Wishmaster::PostPtr post = chanlist()->getPost(postId);
		if(!post)
			return;

		if(post->attachment(attachmentIndex)->type().startsWith("image"))
		{
			if(m_imageView)
			{
				m_imageView->show();
				showAttachment(postId, attachmentIndex);
			}
		}
	}

	void StandardChanManager::refreshImageView(const QImage& image, const QByteArray& postHid)
	{
		if(m_imageView && m_imageView->hidExists(postHid))
			m_imageView->showStaticImage(image, postHid);
	}

	void StandardChanManager::refreshImageView(const QByteArray& arr, const QByteArray& postHid)
	{
		if(m_imageView && m_imageView->hidExists(postHid))
			m_imageView->showAnimatedImage(arr, postHid);
	}

	void StandardChanManager::refreshImageView(const ExtraContainer& extra, const QByteArray& postHid)
	{
		if(m_imageView && m_imageView->hidExists(postHid))
			m_imageView->setExtraContainer(extra, postHid);
	}

	void StandardChanManager::progressUpdate(int total)
	{
		if((total == 0) || (total == 100))
		{
			if(m_view->progressBar()->isVisible())
				m_view->progressBar()->hide();
		}
		else
		{
			if(!m_view->progressBar()->isVisible())
				m_view->progressBar()->show();
		}
		m_view->progressBar()->setValue(total);
	}

	void StandardChanManager::swap()
	{
		for(int i = 0; i < chanlist()->chansCount(); i++)
		{
			chanlist()->getChan(i)->swap();
		}
	}

	FilterListPtr StandardChanManager::globalFilterList()
	{
		return m_filterList;
	}

	void StandardChanManager::setChanlistFilename(const QString& filename)
	{
		m_chanlistFile = filename;
	}

	void StandardChanManager::loadBoard(Chan* parent, ChanAgent* ag, const QString& parserName, const QDomNode& board, const QString& timestampFormat)
	{
		if(board.nodeName() == "board")
		{
			QString board_disabled = board.attributes().namedItem("disabled").toAttr().value();
			if(board_disabled == "true")
				return;

			QString boardname = board.attributes().namedItem("link").toAttr().value();
			// FIXME:
			QString hostprefix = board.attributes().namedItem("hostprefix").toAttr().value();
			if(!hostprefix.isEmpty())
				ag->setProperty(QString("hostprefix" + boardname).toUtf8().data(), hostprefix);

			QString posthostprefix = board.attributes().namedItem("posthostprefix").toAttr().value();
			if(!posthostprefix.isEmpty())
				ag->setProperty(QString("posthostprefix" + boardname).toUtf8().data(), posthostprefix);

			log(llDebug, "Board: %s (%s)", qPrintable(board.attributes().namedItem("link").toAttr().value()),
					qPrintable(ag->property(QString("hostprefix" + boardname).toUtf8().data()).toString()));
			Board* newBoard = new Board(boardname, parent);
			newBoard->setAlias(board.attributes().namedItem("alias").toAttr().value());
			BoardParser* parser = ParserFactory::makeParser(parserName, hostprefix + parent->getHost() + posthostprefix, boardname);
			if(!parser)
			{
				log(llError, "Unable to create parser: %s", qPrintable(parserName));
				delete newBoard;
				return;
			}
			parser->setTimestampFormat(timestampFormat);
			newBoard->setParser(parser);
			newBoard->loadSettings();
			parent->addBoard(newBoard);

			BoardConstraints constraints = parent->constraints();
			QDomNamedNodeMap map = board.attributes();
			for(int i = 0; i < map.size(); i++)
			{
				QDomAttr a = map.item(i).toAttr();
				fillBoardConstraintsField(a.name(), a.value(), constraints);
			}
			newBoard->setConstraints(constraints);
		}
		else if(board.nodeName() == "agentsettings")
		{
			QDomNamedNodeMap map = board.attributes();
			for(int k = 0; k < map.size(); k++)
			{
				QDomAttr n = map.item(k).toAttr();
				ag->setProperty(n.name().toUtf8().data(), n.value());
			}
		}
		else if(board.nodeName() == "captcha")
		{
			QDomNamedNodeMap map = board.attributes();
			QString captchaFetcherId = map.namedItem("fetcher").toAttr().value();
			QVariantMap params;
			for(int k = 0; k < map.size(); k++)
			{
				QDomAttr n = map.item(k).toAttr();
				params.insert(n.name(), n.value());
			}
			CaptchaFetcher* fetcher = m_captchaFetcherFactory->makeCaptchaFetcher(captchaFetcherId, m_requestManager, params);
			if(fetcher)
				ag->setCaptchaFetcher(fetcher);
		}
	}

	bool StandardChanManager::fillBoardConstraintsField(const QString& field,
												const QString& value, BoardConstraints& constraints)
	{
		if(field == "chars_max")
		{
			constraints.charsMax = value.toInt();
			return true;
		}
		else if(field == "oppost_pics_min")
		{
			constraints.oppostPicsMin = value.toInt();
			return true;
		}
		else if(field == "oppost_pics_max")
		{
			constraints.oppostPicsMax = value.toInt();
			return true;
		}
		else if(field == "post_pics_min")
		{
			constraints.postPicsMin = value.toInt();
			return true;
		}
		else if(field == "post_pics_max")
		{
			constraints.postPicsMax = value.toInt();
			return true;
		}
		else if(field == "email_field")
		{
			constraints.emailField = value == "true";
		}
		else if(field == "sage_checkbox")
		{
			constraints.sageCheckbox = value == "true";
		}
		return false;
	}

	void StandardChanManager::parseArgs(const QDomElement& n, Chan* chan)
	{
		BoardConstraints constraints;
		QDomNamedNodeMap map = n.attributes();
		for(int i = 0; i < map.size(); i++)
		{
			QDomAttr a = map.item(i).toAttr();
			if(fillBoardConstraintsField(a.name(), a.value(), constraints))
				continue;
			if(a.name() == "captchakeymap")
			{
				if(!a.value().isEmpty())
					chan->setKeymap(m_keymapManager.createKeymap(a.value()));
			}
			else if(a.name() == "url" ||
					a.name() == "agent" ||
					a.name() == "parser")
			{
				// Do nothing
			}
			else
			{
				chan->setProperty(a.name().toUtf8().data(), a.value());
			}
		}
		chan->setConstraints(constraints);
	}

	void StandardChanManager::showAttachment(const PostId& postId, int index)
	{
		PostPtr post = chanlist()->getPost(postId);
		if(!post)
			return;
		QString attachmentType = post->attachment(index)->type();
		if(attachmentType.startsWith("image"))
		{
			QByteArray hashId = postId.toString().toUtf8() + '/' + QByteArray::number(index);
			if(!attachmentType.contains("gif")) // FIXME hack
				m_imageView->showStaticImage(QImage(), hashId);
			else
				m_imageView->showAnimatedImage(QByteArray(), hashId);
			if(post->attachment(index)->image().isNull())
			{
				//m_imageView->getUI()->list_tags->clear();
				requestPic(postId, index);
			}
			else
			{
				if(!attachmentType.contains("gif")) // FIXME hack
					m_imageView->showStaticImage(post->attachment(index)->image(), hashId);
				else
					m_imageView->showAnimatedImage(post->attachment(index)->data(), hashId);

				m_imageView->setExtraContainer(post->extraContainer(), hashId);
			}
		}
	}

	void StandardChanManager::chanIconChanged(const QString& host)
	{
		for(int i = 0; i < chanlist()->chansCount(); i++)
		{
			if(chanlist()->getChan(i)->getHost() == host)
			{
				m_view->chanIconChanged(i);
				return;
			}
		}
	}

	void StandardChanManager::newMessages(const PostId& id)
	{
		m_newMsgTimer.start();
		m_newMsgThreads.append(id);
	}

	void StandardChanManager::displayNewMsgNotification()
	{
		QString str = tr("New messages in:\n");
		bool first = true;
		foreach(const PostId& id, m_newMsgThreads)
		{
			if(!first)
				str += QChar('\n');
			first = false;
			ThreadPtr thread = chanlist()->getChan(id.chan())->board(id.board())->getThread(id.getThreadId());
			str += tr("Chan: %1, board: %2, thread: %3").arg(id.chan()).arg(id.board()).arg(thread->id().threadNumericId());
			if(!thread->getName().isEmpty())
			{
				if(thread->getName().trimmed().size() > 25) // FIXME hardcoded
					str += tr("(%1...)").arg(thread->getName().trimmed().left(25));
				else
					str += tr("(%1)").arg(thread->getName().trimmed());
			}
		}

		NotificationManager::instance()->newMessagesNotification(str);
		m_newMsgThreads.clear();
	}

	ChanList* StandardChanManager::chanlist() const
	{
		return m_chanList;
	}

	ChanWatcher* StandardChanManager::chanwatcher() const
	{
		return m_watcher;
	}

	void StandardChanManager::setFilterList(const FilterListPtr& list)
	{
		m_filterList = list;
	}

	FilterListPtr StandardChanManager::filterList() const
	{
		return m_filterList;
	}

	void StandardChanManager::setThreadStorage(ThreadStorage* storage)
	{
		if(m_threadStorage)
			delete m_threadStorage;
		m_threadStorage = storage;
	}

	ThreadStorage* StandardChanManager::threadStorage() const
	{
		return m_threadStorage;
	}

	Wishmaster2::Network::RequestManager* StandardChanManager::requestManager() const
	{
		return m_requestManager;
	}

	void StandardChanManager::postingDone(const ThreadId& threadId)
	{
		log(llDebug, "Posting done");
		if(threadId.threadNumericId() > 0)
			requestThread(threadId);
		else
			requestBoardIndex(threadId.chanName(), threadId.boardId(), 0);
	}

	void StandardChanManager::slot_captcha(const ThreadId& threadId, const QByteArray& data)
	{
		emit captcha(threadId, data);
	}

} // namespace Wishmaster
